/*
  Copyright (c) 2011-2012 - Tőkés Attila

  This file is part of SmtpClient for Qt.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  See the LICENSE file for more details.
*/

#include "smtpclient.h"

#include <QFileInfo>
#include <QByteArray>
#include <QMetaType>
//#include <QMetaTypeId>


/* [1] Constructors and destructors */

SmtpClient::SmtpClient(const QString & host, int port, ConnectionType connectionType) :
    name("localhost"),
    authMethod(AuthPlain),
    connectionTimeout(30000),
    responseTimeout(30000)
{
   // Q_DECLARE_METATYPE(ConnectionType);
    qRegisterMetaType<int>("SmtpError");
    setConnectionType(connectionType);

    this->host = host;
    this->port = port;

    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
//    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
//            this, SLOT(socketError(QAbstractSocket::SocketError)));
    connect(socket, SIGNAL(readyRead()),
            this, SLOT(socketReadyRead()));
    QSslSocket::setDefaultCaCertificates(QSslSocket::systemCaCertificates());

}

SmtpClient::~SmtpClient() {}

/* [1] --- */


/* [2] Getters and Setters */

void SmtpClient::setUser(const QString &user)
{
    this->user = user;
}

void SmtpClient::setPassword(const QString &password)
{
    this->password = password;
}

void SmtpClient::setAuthMethod(AuthMethod method)
{
    this->authMethod = method;
}

void SmtpClient::setHost(QString &host)
{
    this->host = host;
}

void SmtpClient::setPort(int port)
{
    this->port = port;
}

void SmtpClient::setConnectionType(ConnectionType ct)
{
    this->connectionType = ct;

    switch (connectionType)
    {
    case TcpConnection:
        socket = new QTcpSocket(this);
        break;
    case SslConnection:
    case TlsConnection:
    {
        ssl_socket = new QSslSocket(this);
        socket=ssl_socket;
        ssl_socket->setPeerVerifyMode(QSslSocket::VerifyNone);
        //;
        //connect(ssl_socket,
        //        SIGNAL(sslErrors(QList<QSslError>)),
        //        this,
        //        SLOT(sslErrorHandler(QList<QSslError>)));
        break;
    }
    }

   // qDebug() << "Support SSL:  " << QSslSocket::supportsSsl()
    //        << "\nLib Version Number: " << QSslSocket::sslLibraryVersionNumber()
            //<< "\nLib Version String: " << QSslSocket::sslLibraryVersionString();
           // << "\nLib Build Version Number: " << QSslSocket::sslLibraryBuildVersionNumber()
           // << "\nLib Build Version String: " << QSslSocket::sslLibraryBuildVersionString();
}

const QString& SmtpClient::getHost() const
{
    return this->host;
}

const QString& SmtpClient::getUser() const
{
    return this->user;
}

const QString& SmtpClient::getPassword() const
{
    return this->password;
}

SmtpClient::AuthMethod SmtpClient::getAuthMethod() const
{
    return this->authMethod;
}

int SmtpClient::getPort() const
{
    return this->port;
}

SmtpClient::ConnectionType SmtpClient::getConnectionType() const
{
    return connectionType;
}

const QString& SmtpClient::getName() const
{
    return this->name;
}

void SmtpClient::setName(const QString &name)
{
    this->name = name;
}

const QString & SmtpClient::getResponseText() const
{
    return responseText;
}

int SmtpClient::getResponseCode() const
{
    return responseCode;
}

QTcpSocket* SmtpClient::getSocket() {
    return socket;
}

/* [2] --- */


/* [3] Public methods */

bool SmtpClient::connectToHost()
{
    switch (connectionType)
    {
    case TlsConnection:
    case TcpConnection:
        socket->connectToHost(host, port);
        break;
    case SslConnection:
        ((QSslSocket*) socket)->connectToHostEncrypted(host, port);
        break;

    }

    // Tries to connect to server
    if (!socket->waitForConnected(connectionTimeout))
    {
        //((QSslSocket*)socket)->ignoreSslErrors();

        //qDebug()<<socket->errorString();
        //if(!Error(ConnectionTimeoutError))
            return false;
    }

    try
    {
        // Wait for the server's response
        waitForResponse();

        // If the response code is not 220 (Service ready)
        // means that is something wrong with the server
        if (responseCode != 220)
        {
            //qDebug()<<socket->errorString();
            //if(!Error(ServerError))
                return false;
        }

        // Send a EHLO/HELO message to the server
        // The client's first command must be EHLO/HELO
        sendMessage("EHLO " + name);

        // Wait for the server's response
        waitForResponse();

        // The response code needs to be 250.
        if (responseCode != 250)
        {
            //qDebug()<<socket->errorString();
            //if(!Error(ServerError))
                return false;
        }

        if (connectionType == TlsConnection) {
            // send a request to start TLS handshake
            sendMessage("STARTTLS");

            // Wait for the server's response
            waitForResponse();

            // The response code needs to be 220.
            if (responseCode != 220)
            {
                //qDebug()<<socket->errorString();
                //if(!Error(ServerError))
                    return false;
            };

            ((QSslSocket*) socket)->startClientEncryption();

            if (!((QSslSocket*) socket)->waitForEncrypted(connectionTimeout))
            {
                //qDebug() << ((QSslSocket*) socket)->errorString();
                //if(!Error(ConnectionTimeoutError))
                    return false;
            }

            // Send ELHO one more time
            sendMessage("EHLO " + name);

            // Wait for the server's response
            waitForResponse();

            // The response code needs to be 250.
            if (responseCode != 250)
            {
                //if(!Error(ServerError))
                //qDebug()<<socket->errorString();
                    return false;
            }
        }
    }
    catch (ResponseTimeoutException)
    {
        return false;
    }

    // If no errors occured the function returns true.
    return true;
}

bool SmtpClient::login()
{
    return login(user, password, authMethod);
}

bool SmtpClient::login(const QString &user, const QString &password, AuthMethod method)
{
    try {
        if (method == AuthPlain)
        {
            // Sending command: AUTH PLAIN base64('\0' + username + '\0' + password)
            sendMessage("AUTH PLAIN " + QByteArray().append((char) 0).append(user).append((char) 0).append(password).toBase64());

            // Wait for the server's response
            waitForResponse();

            // If the response is not 235 then the authentication was faild
            if (responseCode != 235)
            {
                //if(!Error(AuthenticationFailedError))
                //qDebug()<<socket->errorString();
                    return false;
            }
        }
        else if (method == AuthLogin)
        {
            // Sending command: AUTH LOGIN
            sendMessage("AUTH LOGIN");

            // Wait for 334 response code
            waitForResponse();
            if (responseCode != 334)
            {
                //qDebug()<<socket->errorString();
               // if(!Error(AuthenticationFailedError))
                    return false;
            }

            // Send the username in base64
            sendMessage(QByteArray().append(user).toBase64());

            // Wait for 334
            waitForResponse();
            if (responseCode != 334)
            {
                //qDebug()<<socket->errorString();
               // if(!Error(AuthenticationFailedError))
                    return false;
            }

            // Send the password in base64
            sendMessage(QByteArray().append(password).toBase64());

            // Wait for the server's responce
            waitForResponse();

            // If the response is not 235 then the authentication was faild
            if (responseCode != 235)
            {
                //qDebug()<<socket->errorString();
               // if(!Error(AuthenticationFailedError))
                    return false;
            }
        }
    }
    catch (ResponseTimeoutException e)
    {
        // Responce Timeout exceeded
        //qDebug()<<socket->errorString();
        //if(!Error(AuthenticationFailedError))
            return false;
    }

    return true;
}

bool SmtpClient::sendMail(MimeMessage& email)
{
    try
    {
        // Send the MAIL command with the sender
        sendMessage("MAIL FROM: <" + email.getSender().getAddress() + ">");

        waitForResponse();

        if (responseCode != 250) return false;

        // Send RCPT command for each recipient
        QList<EmailAddress*>::const_iterator it, itEnd;
        // To (primary recipients)
        for (it = email.getRecipients().begin(), itEnd = email.getRecipients().end();
             it != itEnd; ++it)
        {
            sendMessage("RCPT TO: <" + (*it)->getAddress() + ">");
            waitForResponse();

            if (responseCode != 250) return false;
        }

        // Cc (carbon copy)
        for (it = email.getRecipients(MimeMessage::Cc).begin(), itEnd = email.getRecipients(MimeMessage::Cc).end();
             it != itEnd; ++it)
        {
            sendMessage("RCPT TO: <" + (*it)->getAddress() + ">");
            waitForResponse();

            if (responseCode != 250) return false;
        }

        // Bcc (blind carbon copy)
        for (it = email.getRecipients(MimeMessage::Bcc).begin(), itEnd = email.getRecipients(MimeMessage::Bcc).end();
             it != itEnd; ++it)
        {
            sendMessage("RCPT TO: <" + (*it)->getAddress() + ">");
            waitForResponse();

            if (responseCode != 250) return false;
        }

        // Send DATA command
        sendMessage("DATA");
        waitForResponse();

        if (responseCode != 354) return false;

        sendMessage(email.toString());

        // Send \r\n.\r\n to end the mail data
        sendMessage(".");

        waitForResponse();

        if (responseCode != 250) return false;
    }
    catch (ResponseTimeoutException)
    {
        return false;
    }

    return true;
}

void SmtpClient::quit()
{
    sendMessage("QUIT");
}

/* [3] --- */


/* [4] Protected methods */

void SmtpClient::waitForResponse() throw (ResponseTimeoutException)
{
    if (!socket->waitForReadyRead(responseTimeout))
    {
        //qDebug()<<socket->errorString();
        //if(!Error(ResponseTimeoutError))
            throw ResponseTimeoutException();
    }

    // Save the server's response
    responseText = socket->readAll();
    qDebug()<<responseText;

    // Extract the respose code from the server's responce (first 3 digits)
    responseCode = responseText.left(3).toInt();

    //if (responseCode / 100 == 4)
    //{
        //qDebug()<<socket->errorString();
        //Error(ServerError);
    //}

    //if (responseCode / 100 == 5)
    //    //qDebug()<<socket->errorString();
        //Error(ClientError);
}

void SmtpClient::sendMessage(const QString &text)
{
    qDebug()<<(text.length()>120?text.left(120)+"...":text);
    //qDebug()<<text;
    int len=text.length();
    int pos=0;
    int send_buf=64*1024;
    while(pos<len)
    {
        socket->write(text.mid(pos,send_buf).toUtf8());
        if(!socket->waitForBytesWritten(responseTimeout))
            return;
        pos+=send_buf;
    }
    socket->write(QString("\r\n").toUtf8());
}

//bool SmtpClient::Error(int code)
//{
   // ((QSslSocket*)socket)->ignoreSslErrors();
   // qDebug()<<socket->errorString();
//    return false;/
//}

/* [4] --- */


/* [5] Slots for the socket's signals */

void SmtpClient::socketStateChanged(QAbstractSocket::SocketState state)
{
}

void SmtpClient::socketError(QAbstractSocket::SocketError socketError)
{
}

void SmtpClient::socketReadyRead()
{
}

void SmtpClient::sslErrorHandler(QList<QSslError> errlist)
{
    foreach (QSslError err, errlist)
      qDebug() << "ssl error: " << err;
    ssl_socket->ignoreSslErrors();
}

/* [5] --- */




